﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using Lesk;
using Infinity.ExpressionParser.Tokens;
using Infinity.ExpressionParser.Parsers;
using System.Reflection;
using Infinity.ExpressionParser.Tokens.Separators;
using Infinity.Expressions.Core;
using Infinity.ExpressionParser.Comparer;
using System.Collections;
using Infinity.ExpressionParser.Helpers;
using Infinity.ExpressionParser.Tokens.Operators.Quantifier;
using Infinity.ExpressionParser.Tokens.Operators.Unary;

namespace Infinity.ExpressionParser
{
	public class ExpressionTreeBuilder
	{
		public ExpressionTreeBuilder()
		{
			Variables = new List<ParameterExpression>();
			Functions = new FunctionCollection();
			Tokenizer = new Tokenizer();
		}

		public IList<ParameterExpression> Variables { get; set; }
		public FunctionCollection Functions { get; set; }

		/// <summary>
		/// Tokenizáló, amit a Parse függvény használ
		/// </summary>
		public ITokenizer Tokenizer { get; set; }

		/// <summary>
		/// Karaktersorozat átalakítása Expression-Tree-vé
		/// </summary>
		public Expression Parse(string input)
		{
			if (!string.IsNullOrEmpty(input))
			{
				if (Tokenizer != null)
				{
					try
					{
						var tokenList = Tokenizer.Tokenize(input).Where(token => !(token is WhiteSpaceToken)).ToArray();
						return ToExpression(tokenList);
					}
					catch (UnrecognizedTokenException)
					{
						throw new Exception(); //ismeretlen karakter hiba
					}
				}
				else
				{
					throw new Exception();
				}
			}
			else
			{
				return Expression.Empty();
				//throw new ArgumentException(); //üres input
			}
		}

		/// <summary>
		/// Tokensorozatot Expression-Tree-vé alakít
		/// </summary>
		protected Expression ToExpression(IList<Token> input)
		{
			if (input != null)
			{
				var list = ToPolishNotation(input);
				if (list.Where(token => token is OpenBracketToken).Count() > 0)
				{
					throw new Exception(); //rossz zárójelezés
				}
				else
				{
					return FromPolishNotation(list);
				}
			}
			else
			{
				throw new ArgumentNullException();
			}
		}

		/// <summary>
		/// Tokensorozatot lengyelformává alakítja
		/// </summary>
		protected IList<Token> ToPolishNotation(IList<Token> input)
		{
			Stack<Token> stack = new Stack<Token>();
			List<Token> list = new List<Token>();

			/*Stack<FunctionToken> funcList = new Stack<FunctionToken>();
			Stack<SetToken> setList = new Stack<SetToken>();*/
			Stack<Token> funcList = new Stack<Token>();

			foreach (var token in input)
			{
				if (!(token is OpenRegularBracketToken) && stack.Count > 0 && stack.Peek() is FunctionToken) //a paraméternélküli függvény kiírása az output list-be
				{
					list.Add(stack.Pop());
				}

				if (token is OpenRegularBracketToken)
				{
					if (stack.Count > 0 && stack.Peek() is FunctionToken) //visszanézünk, hogy egy függvény kezdete-e
					{
						(stack.Peek() as FunctionToken).ArgumentsCount = 1;
					}
					stack.Push(token);
				}
				else if (token is CloseRegularBracketToken)
				{
					if (stack.Count > 0)
					{
						var outToken = stack.Pop();
						while (!(outToken is OpenRegularBracketToken))
						{
							list.Add(outToken);
							if (stack.Count > 0)
							{
								outToken = stack.Pop();
							}
							else
							{
								throw new Exception(); //hiba
							}
						}
						if (stack.Count > 0 && stack.Peek() is FunctionToken) //a meghívott függvény argumentumainak megszámolása
						{
							if (funcList != null && funcList.Count > 0)
							{
								funcList.Pop();
								list.Add(stack.Pop());
							}
							else
							{
								throw new Exception(); //hiba
							}
						}
					}
					else
					{
						throw new Exception(); //rossz zárójelezés
					}
				}
				else if (token is OpenIndexerBracketToken) //indexelés kezdete
				{
					stack.Push(token);
				}
				else if (token is CloseIndexerBracketToken) //indexelés bezárása
				{
					if (stack.Count > 0)
					{
						var popToken = stack.Pop();
						while (!(popToken is OpenIndexerBracketToken))
						{
							list.Add(popToken);
							if (stack.Count > 0)
							{
								popToken = stack.Pop();
							}
							else
							{
								throw new Exception(); //indexer hiba
							}
						}
						if (stack != null && stack.Count > 0 && stack.Peek() is FunctionToken) //a meghívott függvény argumentumainak megszámolása
						{
							if (funcList != null && funcList.Count > 0)
							{
								funcList.Pop();
								list.Add(stack.Pop());
							}
							else
							{
								throw new Exception(); //hiba
							}
						}
						list.Add(new IndexerToken());
					}
				}
				else if (token is OpenSetBracketToken)
				{
					stack.Push(token);
					funcList.Push(new SetToken());
				}
				else if (token is CloseSetBracketToken)
				{
					if (stack.Count > 0)
					{
						var popToken = stack.Pop();
						while (!(popToken is OpenSetBracketToken))
						{
							list.Add(popToken);
							if (stack.Count > 0)
							{
								popToken = stack.Pop();
							}
							else
							{
								throw new Exception(); //indexer hiba
							}
						}
						if (funcList != null && funcList.Count > 0)
						{
							list.Add(funcList.Pop());
						}
						else
						{
							throw new Exception(); //halmaz hiba
						}
					}
				}
				else if (token is OperatorToken)
				{
					if ((token as OperatorToken).BracketDirection == BracketDirection.Left)
					{
						while (stack.Count > 0 &&
							   stack.Peek() is OperatorToken &&
							   (token as OperatorToken).Precedence <= (stack.Peek() as OperatorToken).Precedence)
						{
							var popToken = stack.Pop();
							list.Add(popToken);
						}
					}
					else
					{
						while (stack.Count > 0 &&
							   stack.Peek() is OperatorToken &&
							   (token as OperatorToken).Precedence < (stack.Peek() as OperatorToken).Precedence)
						{
							var popToken = stack.Pop();
							list.Add(popToken);
						}
					}
					if (token is QuantifierToken)
					{
						Variables.Add(Expression.Parameter(typeof(object), (token as QuantifierToken).VariableName));
					}
					stack.Push(token);
				}
				else if (token is IdentifierToken)
				{
					if (Variables == null || Variables.Where(x => x.Name == token.Value).Count() == 0) //Nem változó => biztosan függvény
					{
						funcList.Push(new FunctionToken() { Value = token.Value, ArgumentsCount = 0 });
						stack.Push(funcList.Peek());
					}
					else //Változó
					{
						list.Add(new VariableToken() { Value = token.Value });
					}
				}
				else if (token is ColonOperatorToken)
				{
					stack.Push(token);
				}
				else if (token is CommaSeparatorToken)
				{
					if (funcList != null && funcList.Count > 0)
					{
						if (funcList.Peek() is FunctionToken)
						{
							(funcList.Peek() as FunctionToken).ArgumentsCount++;
						}
						else if (funcList.Peek() is SetToken)
						{
							var peek = funcList.Peek() as SetToken;
							if (peek.Count == 0)
							{
								peek.Count = 2;
							}
							else
							{
								peek.Count++;
							}
						}
					}
					else
					{
						throw new Exception(); //HIBA
					}
				}
				else
				{
					list.Add(token);
				}
			}
			while (stack.Count > 0)
			{
				var popToken = stack.Pop();
				list.Add(popToken);
			}
			return list;
		}

		/// <summary>
		/// Lengyelformájú tokensorozatot Expression-Tree-vé alakít
		/// </summary>
		protected Expression FromPolishNotation(IList<Token> list)
		{
			Stack<Expression> stack = new Stack<Expression>(); //a lengyel forma feldolgozásához szükséges verem
			List<ISet<object>> setList = new List<ISet<object>>(); //számontartja a halmazokat

			foreach (var token in list)
			{
				if (token is OperatorToken)
				{
					if (token is AssignmentToken)
					{
						if (stack.Count > 1)
						{
							var right = stack.Pop();
							var left = stack.Pop();
							stack.Push(AssignmentParser.ToExpression(token, left, right));
						}
						else
						{
							throw new Exception(); //hiba
						}
					}
					else if (token is UnaryOperatorToken)
					{
						if (stack.Count > 0)
						{
							var arg = stack.Pop();
							stack.Push(UnaryParser.ToExpression(token, arg));
						}
						else
						{
							throw new Exception(); //hiba
						}
					}
					else if (token is BinaryOperatorToken)
					{
						if (stack.Count > 1)
						{
							var right = stack.Pop();
							var left = stack.Pop();
							stack.Push(BinaryParser.ToExpression(token, left, right));
						}
						else
						{
							throw new Exception(); //hiba
						}
					}
					else if (token is QuantifierToken)
					{
						if (stack.Count > 1)
						{
							var variable = Variables.Where(exp => exp.Name == (token as QuantifierToken).VariableName).Last();
							var condition = stack.Pop();
							var set = stack.Pop();
							stack.Push(QuantifierParser.ToExpression(token as QuantifierToken, variable, set, condition));
						}
						else
						{
							throw new Exception(); //nem volt valid a szintaxis
						}
					}
				}
				else
				{
					if (token is ValueToken)
					{
						stack.Push(ConstantParser.ToExpression(token));
					}
					else if (token is IndexerToken)
					{
						if (stack.Count > 1)
						{
							var index = stack.Pop();
							var array = stack.Pop();
							stack.Push(IndexerParser.ToExpression(array, index));
						}
						else
						{
							throw new Exception(); //indexelési hiba
						}
					}
					else if (token is IdentifierToken)
					{
						if (token is VariableToken)
						{
							var varList = Variables.Where(x => token.Value == x.Name);
							if (varList.Count() == 1)
							{
								stack.Push(varList.First());
							}
							else if (varList.Count() > 1) //ha több ugyanolyan nevű változót találtunk akkor a legutóbb deklaráltat tesszük bele
							{
								stack.Push(varList.Last());
							}
							else
							{
								throw new Exception(); //nem deklarált változó
							}
						}
						else if (token is FunctionToken)
						{
							List<MethodInfo> funcList;
							bool match = false;
							if (Functions.TryGetValue(token.Value, out funcList))
							{
								foreach (var func in funcList)
								{
									var args = new List<Expression>();
									var paramList = func.GetParameters();

									if (paramList.Length > 0 && paramList.Last().ParameterType.IsArray)
									{
										int arrayCount = (token as FunctionToken).ArgumentsCount - paramList.Length + 1;
										if (arrayCount >= 0)
										{
											List<Expression> funcArgs = new List<Expression>();
											var funcArgsCount = (token as FunctionToken).ArgumentsCount;  //a függvény eredeti argumentum száma
											while (stack.Count > 0 && arrayCount != 0)
											{
												funcArgs.Add(stack.Pop());
												arrayCount--;
												(token as FunctionToken).ArgumentsCount--;
											}
											if (arrayCount == 0)
											{
												(token as FunctionToken).ArgumentsCount++;
												funcArgs = funcArgs.Select(exp => Expression.Convert(exp, typeof(object))).ToList<Expression>();
												stack.Push(Expression.NewArrayInit(typeof(object), funcArgs.ToArray()));
											}
											else
											{
												(token as FunctionToken).ArgumentsCount = funcArgsCount;
											}
										}
									}
									if ((token as FunctionToken).ArgumentsCount == paramList.Length) //ha egyezik a paraméterek száma
									{
										foreach (var param in paramList)
										{
											if (stack.Count() > 0)
											{
												var popItem = stack.Pop();
												if (popItem.Type != param.ParameterType)
												{
													if (param.ParameterType.IsPrimitive)
													{
														popItem = Expression.Convert(popItem, param.ParameterType);
													}
													else
													{
														args = null;
														stack.Push(popItem);
														break;
													}
												}
												args.Add(popItem);
											}
											else
											{
												throw new Exception(); // kevés a paraméter
											}
										}

										if (args != null) //jó volt a paraméterek típusa vagy lehetett konvertálni
										{
											args.Reverse();
											stack.Push(Expression.Call(func, args));
											match = true;
											break;
										}
									}
								}
								if (!match) //nem volt találat
								{
									throw new Exception(); //nincs ilyen függvény deklarálva
								}
							}
							else
							{
								throw new Exception(); //nem deklarált függvény
							}
						}
					}
					else if (token is SetToken)
					{
						var set = token as SetToken;
						var hashSet = new HashSet<object>();

						var argsList = new List<Expression>();

						if (stack.Count >= set.Count)
						{
							for (int i = 0; i < set.Count; i++)
							{
								var item = stack.Pop() as Expression;
								if (new[] { typeof(int), typeof(decimal), typeof(uint), typeof(float) }.Contains(item.Type))
								{
									item = Expression.Convert(item, typeof(double));
								}
								argsList.Add(Expression.Convert(item, typeof(object)));
							}
						}

						argsList.Reverse();

						stack.Push(Expression.Call(typeof(SetHelper).GetMethod("ToSet"), Expression.NewArrayInit(typeof(object), argsList)));
					}
					else
					{
						throw new Exception(); //nem deklarált függvény
					}
				}
			}
			if (stack.Count == 1)
			{
				return stack.First();
			}
			else
			{
				throw new Exception();
			}
		}
	}
}
